﻿using System;
using System.Collections.Generic;
using System.Linq;
using D2D2.SharedInterfaces.SharedObject;
using Lidgren.Network;
using Lidgren.Network.Xna;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace D2D2.SharedInterfaces.Networking
{
    public enum DropState
    {
        CurveMotion,
        NoMotion,
        LineMotion,
        EndMotion
    }

    public class Drop : BaseMessage
    {

        private Vector2 _currentLocation;
        private Vector2 _startLocation;
        private Curve _curve;
        private float _motionTime;
        private float _noMotionTime = 0.0f;
        private float _totalMotionTime = 0.6f;
        private const float LINE_SPEED = 1000f;
        private const float PEEK_DELAY = 0.2f;

        private float _totalDeltaX;
        private float _velosity;
        private Vector2 _heading = Vector2.Zero;
        private Vector2 _distance;
        private bool _visible = true;
        private Rectangle _spriteLocation;
        private const float FLASH_TIME = 0.08f;
        private const float TOTAL_NO_MOTION_TIME = 2f;
        private const float START_FLASH_TIME = 0.5f;
        private static Random _rand = new Random();

        private const int WIDTH=20;
        private const int HEIGHT = 20;

        private static List<CurveInfo> _dropCurves;
        private bool _mouseMove;

        static Drop()
        {
            LoadCurves();
        }

        public Drop(NetIncomingMessage message)
            : base(message)
        {
            Rectangle rectangle = new Rectangle((int) InitialSpriteLocation.X, (int) InitialSpriteLocation.Y,
                                                (int) InitialSpriteDimension.X, (int) InitialSpriteDimension.Y);

            _spriteLocation = rectangle;

            Init(CurveNumber);
        }

        public Drop(Rectangle spriteLocation, int curveNumber)
        {
            _spriteLocation = spriteLocation;

            InitialSpriteLocation = new Vector2(spriteLocation.X, spriteLocation.Y);
            InitialSpriteDimension = new Vector2(spriteLocation.Width, spriteLocation.Height);
            CurveNumber = curveNumber;

            Init(curveNumber);
        }

        private void Init(int curveNumber)
        {
            CurveInfo curveInfo = _dropCurves[curveNumber];
            _curve = curveInfo.Curve;

            int x = _spriteLocation.X + _spriteLocation.Width / 2 - WIDTH/2;
            int y = _spriteLocation.Y + _spriteLocation.Height/2 - HEIGHT/2;

            _currentLocation = new Vector2(x, y);
            _startLocation = _currentLocation;

            _totalDeltaX = (_curve.Keys.Last().Position - _curve.Keys.First().Position) * curveInfo.Sign;

            _velosity = _totalDeltaX / _totalMotionTime;

            State = DropState.CurveMotion;
        }

        public Vector2 InitialSpriteLocation { get; set; }
        public Vector2 InitialSpriteDimension { get; set; }
        public int CurveNumber { get; set; }

        public DropState State { get; private set; }
        public Texture2D Texture { get; set; }

        private Rectangle CurrentRectangle { get { return new Rectangle((int)_currentLocation.X, (int)_currentLocation.Y, WIDTH, HEIGHT); } }

        public static List<int> GetRandomDropCurves()
        {
            List<KeyValuePair<int, double>> list = new List<KeyValuePair<int, double>>();

            for (int i = 0; i < 8; i++)
            {
                list.Add(new KeyValuePair<int, double>(i, _rand.NextDouble()));
            }

            var numbers = list.OrderBy(k => k.Value).ToList();

            return new List<int>
                       {
                           numbers[0].Key,
                           numbers[1].Key,
                           numbers[2].Key,
                           numbers[3].Key,
                           numbers[4].Key,
                           numbers[5].Key,
                           numbers[6].Key,
                           numbers[7].Key
                       };
        }

        private static void LoadCurves()
        {
            Curve rightCurve = new Curve();
            rightCurve.Keys.Add(new CurveKey(0, 0, 0, 0.7f));
            rightCurve.Keys.Add(new CurveKey(0.5f, 0.5f, 0, 0));
            rightCurve.Keys.Add(new CurveKey(1, -0.5f, -0.66f, 0.2f));
            rightCurve.Keys.Add(new CurveKey(1, -0.3f, 0, 0));
            rightCurve.Keys.Add(new CurveKey(1.2f, -0.5f, -0.2f, -2.2f));

            Curve leftCurve = new Curve();
            leftCurve.Keys.Add(new CurveKey(-1.2f, -0.5f, 0, 0.2f));
            leftCurve.Keys.Add(new CurveKey(-1.1f, -0.3f, 0, 0));
            leftCurve.Keys.Add(new CurveKey(-1, -0.5f, -0.2f, 1));
            leftCurve.Keys.Add(new CurveKey(-0.5f, 0.7f, 0, 0));
            leftCurve.Keys.Add(new CurveKey(0, 0, -0.5f, -0));

            _dropCurves = new List<CurveInfo>();
            _dropCurves.Add(new CurveInfo { Curve = rightCurve, Direction = CurveDirection.Right });
            _dropCurves.Add(new CurveInfo { Curve = leftCurve, Direction = CurveDirection.Left });

            for (int c = 1; c <= 3; c++)
            {
                Curve newRigntCurve = new Curve();
                newRigntCurve.Keys.Add(new CurveKey(0, 0, 0, 0.5f));

                Curve newLeftCurve = new Curve();

                for (int k = 1; k <= 4; k++)
                {
                    CurveKey rightKey = rightCurve.Keys[k];
                    CurveKey newRightKey = new CurveKey(rightKey.Position - c * 0.1f, rightKey.Value, rightKey.TangentIn,
                                                        rightKey.TangentOut);
                    newRigntCurve.Keys.Add(newRightKey);

                    CurveKey leftKey = leftCurve.Keys[k - 1];
                    CurveKey newLeftKey = new CurveKey(leftKey.Position + c * 0.1f, leftKey.Value, leftKey.TangentIn,
                                                       leftKey.TangentOut);
                    newLeftCurve.Keys.Add(newLeftKey);
                }

                newLeftCurve.Keys.Add(new CurveKey(0, 0, -0.5f, -0));

                _dropCurves.Add(new CurveInfo { Curve = newRigntCurve, Direction = CurveDirection.Right });
                _dropCurves.Add(new CurveInfo { Curve = newLeftCurve, Direction = CurveDirection.Left });
            }
        }

        public bool Contains(int mouseX, int mouseY)
        {
            return CurrentRectangle.Contains(mouseX, mouseY);
        }

        public void Update(int mouseX, int mouseY)
        {
            _mouseMove = Contains(mouseX, mouseY);
        }

        public void Update(float elapsed)
        {
            _motionTime += elapsed;

            if (_mouseMove && State != DropState.LineMotion && _motionTime > PEEK_DELAY)
            {
                State = DropState.LineMotion;
                _motionTime = 0;

                _startLocation = _currentLocation;
                _heading = GlobalConst.MONEY_LOCATION - new Vector2(_currentLocation.X, _currentLocation.Y);
                _distance = _heading;
                if (_heading != Vector2.Zero) _heading.Normalize();
            }

            if (State == DropState.LineMotion)
            {

                Vector2 lineVelosity = _heading * LINE_SPEED * _motionTime;
                if (lineVelosity.Length() >= _distance.Length())
                {
                    State = DropState.EndMotion;
                    return;
                }

                _currentLocation = new Vector2(_startLocation.X + (int) lineVelosity.X,
                                               _startLocation.Y + (int) lineVelosity.Y);

            }

            if (State == DropState.CurveMotion)
            {

                if (_motionTime > _totalMotionTime)
                {
                    State = DropState.NoMotion;
                    _motionTime = 0;
                    return;
                }


                float deltaX = _motionTime * _velosity;

                float deltaY = _curve.Evaluate(deltaX);

                deltaX = deltaX * _spriteLocation.Width;
                deltaY = deltaY * _spriteLocation.Height;

                _currentLocation = new Vector2(_startLocation.X + (int)deltaX, _startLocation.Y - (int)deltaY);
            }

            if (State == DropState.NoMotion)
            {
                if (_motionTime > TOTAL_NO_MOTION_TIME)
                {
                    State = DropState.EndMotion;
                    return;
                }

                if (_motionTime > START_FLASH_TIME)
                {
                    _noMotionTime += elapsed;
                    if (_noMotionTime > FLASH_TIME)
                    {
                        _noMotionTime = 0;
                        _visible = !_visible;
                    }
                }
            }
        }

        public void Draw(SpriteBatch spriteBatch)
        {
            if (Texture == null) return;

            if (_visible || State != DropState.NoMotion)
                spriteBatch.Draw(Texture, _currentLocation, new Rectangle(0, 0, 20, 20), Color.White);
        }

        #region Implementation of IGameMessage

        public override GameMessageType MessageType
        {
            get { return GameMessageType.DropState;}
        }

        public override void Encode(NetOutgoingMessage message)
        {
            message.Write(Id);
            message.Write(InitialSpriteLocation);
            message.Write(InitialSpriteDimension);
            message.Write(CurveNumber);
        }

        public override void Decode(NetIncomingMessage message)
        {
            Id = message.ReadInt64();
            InitialSpriteLocation = message.ReadVector2();
            InitialSpriteDimension = message.ReadVector2();
            CurveNumber = message.ReadInt32();
        }

        #endregion
    }
   
}
